﻿using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Reflection;



using edu.stanford.nlp.parser;
using java.util;
using edu.stanford.nlp.ie.crf;
using edu.stanford.nlp.pipeline;
using edu.stanford.nlp.util;
using java.lang;
using edu.stanford.nlp.dcoref;
using edu.stanford.nlp.ling;
using edu.stanford.nlp.trees;
using edu.stanford.nlp.sentiment;
using edu.stanford.nlp.process;

//using edu.stanford.nlp.ling.CoreAnnotations;
//using edu.stanford.nlp.neural.rnn.RNNCoreAnnotations;
//using edu.stanford.nlp.pipeline.Annotation;
//using edu.stanford.nlp.pipeline.StanfordCoreNLP;
//using edu.stanford.nlp.sentiment.SentimentCoreAnnotations;
//using edu.stanford.nlp.trees.Tree;
//using edu.stanford.nlp.util.CoreMap;


namespace MARS.analyticalengine
{
    
    public class MARSanalyticsEngine
    {
        private string profanityfile = "profanitylist.txt";
        private string mildfile = "mild.txt";
        private SortedList rudewordlist = null;
        private SortedList mildwordlist = null;
        StanfordCoreNLP pipeline = null;

#region Constructor and Engine Initialization

        public MARSanalyticsEngine()
        {
            Initialise();
        }

        public void Initialise()
        {
            string cwd = Directory.GetCurrentDirectory();

            try
            {
                string startuppath = System.Windows.Forms.Application.StartupPath;
                string exepath = System.Windows.Forms.Application.ExecutablePath;
                System.Uri uri = new System.Uri(Assembly.GetAssembly(typeof(MARSanalyticsEngine)).CodeBase);
                string classpath = Path.GetDirectoryName(uri.LocalPath);

                Directory.SetCurrentDirectory(classpath);


                java.util.Properties props = new java.util.Properties();
                //None of the following properties helped in solving the NullException problem but leaving this code here 
                //for future learning and research of the StanfordNLP API
                props.put("Xmx600m", "edu.stanford.nlp.pipeline.StanfordCoreNLP");
                props.put("cp", classpath + "edu\\stanford\\nlp\\models\\lexparser\\englishRNN.ser.gz;" + classpath + "\\stanford-parser-3.3.1.jar;" + classpath + "\\stanford-parser-3.3.1-models.jar");
                props.put("pos.model", classpath + "\\edu\\stanford\\nlp\\models\\pos-tagger\\english-bidirectional\\english-bidirectional-distsim.tagger");
                props.put("parser.model", classpath + "\\edu\\stanford\\nlp\\models\\lexparser\\englishRNN.ser.gz");
                props.put("parser.flags", "");
                props.put("encoding", "utf-8");
                props.put("annotators", "tokenize, ssplit,pos, parse, sentiment");

                pipeline = new StanfordCoreNLP(props);

                rudewordlist = LoadProfanityList(classpath+"\\"+profanityfile);
                mildwordlist = LoadProfanityList(classpath+"\\"+mildfile);

            }
            catch (System.Exception e)
            {
                throw e;
            }
            finally
            {
                Directory.SetCurrentDirectory(cwd);
            }


        }

#endregion

#region MARS Analytics Engine Main Interface Methods
        public ProfanityResult ProfanityAnalysis (string text)
        {
            int rudewordcount = 0;
            int mildwordcount =0;
            ProfanityResult pr = new ProfanityResult();
            pr.EmailTone = 0; 
            System.Collections.ArrayList emailwords = extractwords(text);
            foreach(string w in emailwords)
            {
                if(rudewordlist[w] != null)
                {
                    rudewordcount++;
                    pr.Matchprofanewords.Add(w);
                    
                }
                if(mildwordlist[w] != null)
                {
                    mildwordcount++;
                    pr.Matchmildwords.Add(w);
                }

            }
            if (rudewordcount > 0)
            {
                pr.EmailTone = 2;
            }
            else if(mildwordcount >0)
            {
                pr.EmailTone = 1;
            }
            else
            {
                pr.EmailTone = 0;
            }
            pr.ProfaneWordCount = rudewordcount;
            pr.MildWordCount = mildwordcount;
            return pr;


        }


        public StatisticalResult StatisticalAnalysis (string text)
        {
            StatisticalResult sr = new StatisticalResult();
            sr.Lines = CountLinesInString(text);
            sr.Words = WordCount(text);
            sr.Characters = text.Length;
            return sr;
        }

        public SentimentResult SentimentAnalysis(string email,bool bCheckTrailLines,int linestoskip,bool bCheckhyperlinks,bool bCheckDateTime)
        {
            long count = 0;
            
            int SentencesentimentResult = 0;
            int EmailsentimentResult = 0;
//            char[] linedelimiters = { '\n', '.', '?' };
            char[] linedelimiters = { '\n'};
            string[] sentences = email.Split(linedelimiters);
            System.Text.StringBuilder SentimentResultString = new System.Text.StringBuilder();
            SentimentResult sr = new SentimentResult();

            foreach (string sentence in sentences)
            {
                count++;
                if (sentence.Trim().Length != 0)
                {
                    //Check if our current count is greater than the the total sentences minus 4
                    if (!bCheckTrailLines && (count > (sentences.Length - linestoskip)))
                    {
                        SentimentResultString.Append("*** SKIP Last 4 sentences from=>" + sentence.Trim() + "<= NO RESULT:" + "\n");
                        break;
                    }
                    if (!bCheckhyperlinks)
                    {
                        if (DetectHyperlinks(sentence))
                        {
                            SentimentResultString.Append("*** hyperlink SKIP sentence=>" + sentence.Trim() + "<= NO RESULT:"+ "\n");
                            continue;
                        }
                    }
                    if (!bCheckDateTime)
                    {
                        if (DetectDateTime(sentence))
                        {
                            SentimentResultString.Append("*** Date or time found SKIP sentence=>" + sentence.Trim() + "<= NO RESULT:"+ "\n");
                            continue;
                        }
                    }

                    SentencesentimentResult = findSentimentForEachLine(sentence.Trim());
                    SentimentResultString.Append("Sentence=>" + sentence.Trim() + "<= RESULT:" + SentencesentimentResult.ToString() + "\n");
                    sr.SentimentResultsArray.Add((double)SentencesentimentResult);
                    if (SentencesentimentResult < 2)
                        EmailsentimentResult = SentencesentimentResult;
                }
            }

            // richTextBox1.Text = richTextBox1.Text + "\n------------\n" + SentimentResultString;
          //  if (EmailsentimentResult == 0)
            {
                sr.SentimentResultsArray.Sort();
                foreach(double d in sr.SentimentResultsArray)
                {
                    SentimentResultString.Append("[" + d + "],");
                }
                int med = (int)Median(sr.SentimentResultsArray.ToArray(), sr.SentimentResultsArray.Count);
                SentimentResultString.Append("\n****** MEDIAN = " + med + "\n");

                EmailsentimentResult = (int)Mode(sr.SentimentResultsArray.ToArray());
                SentimentResultString.Append("\n****** MODE = " + EmailsentimentResult + "\n");
            }
            sr.Sentiment = EmailsentimentResult;
            sr.Rating = EmailsentimentResult;
            sr.SentimentResultsLogString = SentimentResultString.ToString();

            return sr;
        }
#endregion

#region profanity analysis Methods

        private static SortedList LoadProfanityList(string filename)
        {
            SortedList al = new SortedList();

            try
            {

                using (StreamReader sr = new StreamReader(filename))
                {

                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        //Console.WriteLine("{0}", line);
                        al.Add(line.ToLower().Trim(), line.ToLower().Trim());
                    }
                }

            }
            catch (System.Exception e)
            {
                //Console.WriteLine("error occured: {0} at {1}", e.Message, e.StackTrace);
                MessageBox.Show(e.Message);
            }
            return al;
        }

        static public System.Collections.ArrayList extractwords(string sen)
        {
            int i = 0;
            System.Collections.ArrayList str = new System.Collections.ArrayList();
            char[] delimiter = {' ','\n',',','\t','.','"','\'','?','(',')','{','}','[',']', '&', '!'};
            int index1 = 0;
            int index2;
            Console.WriteLine("extracting: {0}", sen);
            index1 = sen.IndexOfAny(delimiter, index1);
            if (index1 != -1)
            {
                if (index1 != 0)
                {
                    index1 = 0;
                }
                while (index1 < sen.Length)
                {

                    if (sen[index1] == ' ')
                    {
                        index1++;
                    }
                    else
                    {

                        index2 = sen.IndexOfAny(delimiter, index1);
                        if (index2 == -1)
                        {
                            index2 = sen.Length;
                        }
                        int count = index2 - index1;
                        string w = sen.Substring(index1, count);


                        str.Add(w.ToLower().Trim());
                        i++;
                        Console.WriteLine("{0} word found  : {1}", i, w);
                        index1 = index2 + 1;

                    }
                }

            }
            else
            {
                str[i] = sen;
            }

            return str;
        }

#endregion

#region SentimentAnalysis Methods
        
        public int findSentimentForEachLine(string line)
        {

            int mainSentiment = 0;

            if (pipeline == null)
            {
                return 0;
            }

            string cwd = Directory.GetCurrentDirectory();

            try
            {

                string startuppath = System.Windows.Forms.Application.StartupPath;
                string exepath = System.Windows.Forms.Application.ExecutablePath;
                System.Uri uri = new System.Uri(Assembly.GetAssembly(typeof(MARSanalyticsEngine)).CodeBase);
                string classpath = Path.GetDirectoryName(uri.LocalPath);

                Directory.SetCurrentDirectory(classpath);


                java.util.Properties props = new java.util.Properties();
                //None of the following properties helped in solving the NullException problem but leaving this code here 
                //for future learning and research of the StanfordNLP API
                props.put("Xmx600m", "edu.stanford.nlp.pipeline.StanfordCoreNLP");
                props.put("cp", classpath + "edu\\stanford\\nlp\\models\\lexparser\\englishRNN.ser.gz;" + classpath + "\\stanford-parser-3.3.1.jar;" + classpath + "\\stanford-parser-3.3.1-models.jar");
                props.put("pos.model", classpath + "\\edu\\stanford\\nlp\\models\\pos-tagger\\english-bidirectional\\english-bidirectional-distsim.tagger");
                props.put("parser.model", classpath + "\\edu\\stanford\\nlp\\models\\lexparser\\englishRNN.ser.gz");
                props.put("parser.flags", "");
                props.put("encoding", "utf-8");
                props.put("annotators", "tokenize, ssplit,pos, parse, sentiment");

                pipeline = new StanfordCoreNLP(props);

                if (line != null && line.Length > 0)
                {
                    int longest = 0;
                    Annotation annotation = pipeline.process(line);
                    CoreAnnotations.SentencesAnnotation CASA = new CoreAnnotations.SentencesAnnotation();
                    Class cCASA = CASA.getClass();
                    java.util.ArrayList myArrayList = (java.util.ArrayList)annotation.get(cCASA);
                    List<CoreMap> sentences = new List<CoreMap>(myArrayList.size());
                    foreach (CoreMap element in myArrayList)
                    {
                        sentences.Add(element);
                    }
                    foreach (CoreMap sentence in sentences)
                    //            for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.getClass()))
                    {
                        SentimentCoreAnnotations.AnnotatedTree annotatedTree = new SentimentCoreAnnotations.AnnotatedTree();
                        Class cAnnotatedTree = annotatedTree.getClass();
                        Tree tree = (Tree)sentence.get(cAnnotatedTree);

                        int sentiment = edu.stanford.nlp.neural.rnn.RNNCoreAnnotations.getPredictedClass(tree);

                        // string sentimentStr = sentence.get(SentimentCoreAnnotations.getClass());

                        string partText = sentence.ToString();
                        if (partText.Length > longest)
                        {
                            mainSentiment = sentiment;
                            longest = partText.Length;
                        }
                    }
                }
            }   
            catch (System.Exception e)
            {
                throw e;
            }
            finally
            {
                Directory.SetCurrentDirectory(cwd);

            }
            //if (mainSentiment == 2 || mainSentiment > 4 || mainSentiment < 0)
            //{
            //    return mainSentiment;
            //}
        
            return mainSentiment;
        }
        

        public double Median(double[] data, int items)
        {
            int midPoint;
            double median, sum;

            sum = 0.0;

            if (((int)System.Math.Round((double)items / 2.0) * 2) != items)
            {
                midPoint = items / 2;


                sum = data[midPoint];
                sum += data[midPoint + 1];
                sum /= 2.0;
            }
            else
            {
                if (items == 2)
                {
                    sum = data[0] < data[1] ? data[0] : data[1];
                }
                else
                {
                    midPoint = (items / 2) + 1;
                    if (midPoint >= data.Length)
                        midPoint -= 1;
                    sum = data[midPoint];
                }
            }

            median = sum;
            return median;
        }

        public static double GetMedian( double[] sourceNumbers)
        {
            //Framework 2.0 version of this method. there is an easier way in F4        
            if (sourceNumbers == null || sourceNumbers.Length == 0)
                return 0D;

            //make sure the list is sorted, but use a new array
            double[] sortedPNumbers = (double[])sourceNumbers.Clone();
            sourceNumbers.CopyTo(sortedPNumbers, 0);
            Array.Sort(sortedPNumbers);

            //get the median
            int size = sortedPNumbers.Length;
            int mid = size / 2;
            double median = (size % 2 != 0) ? (double)sortedPNumbers[mid] : ((double)sortedPNumbers[mid] + (double)sortedPNumbers[mid - 1]) / 2;
            return median;
        }

        public double Mode(double[] x)
        {
            Dictionary<double, double> counts = new Dictionary<double, double>();
            foreach( double a in x ) {
                if (counts.ContainsKey(a))
                    counts[a] = counts[a] + 1;
                else
                    counts[a] = 1;
            }

            double result = double.MinValue;
            double max = double.MinValue;
            foreach (double key in counts.Keys) {
                if (counts[key] > max) {
                    max = counts[key];
                    result = key;
                }
            }
            return result;
        }
        public bool DetectHyperlinks(string strContent)
        {
            Regex urlregx = new Regex("http[s]*://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&amp;\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?", RegexOptions.IgnoreCase);
            Regex emailregex = new Regex(@"(?<!mailto\:)\b[\w\-]+@[a-z0-9-]+(\.[a-z0-9\-])*\.[a-z]{2,8}\b(?!\<\/a)", RegexOptions.IgnoreCase);
            MatchCollection urlmatches = urlregx.Matches(strContent);
            MatchCollection emailmatches = emailregex.Matches(strContent);

            if (urlmatches.Count > 0  || emailmatches.Count > 0)
                return true;
            else
                return false;
        }

        public bool DetectDateTime(string strContent)
        {
            Match match1;
            Match match2;
            Match match3;
            Match match4;
            Match match5;
            Match match6;

            string year = "";
            string month = "";
            string day = "";
            string separator = "";
            string hour = "";
            string minutes = "";
            string seconds = "";
            string timeSeparator = "";
            string space = "";
            string time = "";
            string time2 = "";
            string time3 = "";
            string regularExpression1 = "";
            string regularExpression2 = "";
            string regularExpression3 = "";
           
            //Samples
            //inputString = "My birth date 1990-10-31 15:05:55,\n" +
            //              "My birth date 31-10-1990 15:05:55,\n" +
            //              "My birth date 10-31-1990 15:05:55";

            year = "([0-9]{4})";
            month = "(0[1-9]|1[012])";
            day = "([0-9]|3[01])";
            separator = "[-/]";

            hour = "(0[1-9]|1[0-9]|2[0-4])";
            minutes = "(0[1-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])";
            seconds = "(0[1-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])";
            timeSeparator = "[:]";
            space = "[ ]";

            time = hour + timeSeparator + minutes + timeSeparator + seconds;
            time2 = hour + timeSeparator + minutes;
            time3 =  @"^(0[1-9]|1[0-2]):[0-5][0-9] [ap]m$";

            regularExpression1 = year + separator + month + separator + day + space + time;
            regularExpression2 = day + separator + month + separator + year + space + time;

            regularExpression3 = month + separator + day + separator + year + space + time;

            match1 = Regex.Match(strContent, regularExpression1, RegexOptions.IgnoreCase);
            match2 = Regex.Match(strContent, regularExpression2, RegexOptions.IgnoreCase);
            match3 = Regex.Match(strContent, regularExpression3, RegexOptions.IgnoreCase);
            match4 = Regex.Match(strContent, time, RegexOptions.IgnoreCase);
            match5 = Regex.Match(strContent, time2, RegexOptions.IgnoreCase);
            match6 = Regex.Match(strContent, time3, RegexOptions.IgnoreCase);

            if ((match1.Length > 0 ) ||
                (match2.Length > 0 ) ||
                (match3.Length > 0 ) ||
                (match4.Length > 0 ) ||
                (match5.Length > 0 ) ||
                (match6.Length > 0 ) )

                return true;
            
            else
                return false;
            
        }

#endregion

#region StatisticalAnalysisMethods

        public long WordCount(string Str)
        {
            MatchCollection collection = Regex.Matches(Str, @"[\S]+");
            return collection.Count;
        }

        static long CountLinesInString(string Str)
        {
            long count = 1;
            int start = 0;
            while ((start = Str.IndexOf('\n', start)) != -1)
            {
                count++;
                start++;
            }
            return count;
        }
#endregion
    }
}
